// $Id: daemon.c 110 2011-11-05 16:09:49Z cedric.shih@gmail.com $
/*
 * Copyright 2011 Cedric Shih (cedric dot shih at gmail dot com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>

#include <zcbuffer.h>

#include "log.h"
#include "request.h"
#include "session.h"
#include "server.h"
#include "url.h"

#include "daemon.h"

#define DEFAULT_READ_TIMEOUT 50
#define DEFAULT_WRITE_TIMEOUT 50

int niortspd_handler(struct niortsp_session *session,
		struct niortsp_request *req, void *cbarg);

typedef int (*niortspd_handler_cb)(struct niortspd *server,
		struct niortsp_session *session, struct niortsp_request *req,
		struct niortsp_media *media, struct niortsp_track *track);

int niortspd_handle_options(struct niortspd *server,
		struct niortsp_session *session, struct niortsp_request *req,
		struct niortsp_media *media, struct niortsp_track *track);

int niortspd_handle_describe(struct niortspd *server,
		struct niortsp_session *session, struct niortsp_request *req,
		struct niortsp_media *media, struct niortsp_track *track);

int niortspd_handle_setup(struct niortspd *server,
		struct niortsp_session *session, struct niortsp_request *req,
		struct niortsp_media *media, struct niortsp_track *track);

int niortspd_handle_play(struct niortspd *server,
		struct niortsp_session *session, struct niortsp_request *req,
		struct niortsp_media *media, struct niortsp_track *track);

int niortspd_handle_teardown(struct niortspd *server,
		struct niortsp_session *session, struct niortsp_request *req,
		struct niortsp_media *media, struct niortsp_track *track);

static struct {
	const char *method;
	niortspd_handler_cb cb;
} handlers[] = {
		{ "OPTIONS", niortspd_handle_options },
		{ "DESCRIBE", niortspd_handle_describe },
		{ "SETUP", niortspd_handle_setup },
		{ "PLAY", niortspd_handle_play },
		{ "TEARDOWN", niortspd_handle_teardown },
};

static unsigned int num_handlers = sizeof(handlers) / sizeof(handlers[0]);

struct niortspd *niortspd_new(struct event_base *evbase)
{
	int rc;
	struct niortspd *server;

	if (!(server = calloc(1, sizeof(*server)))) {
		rc = errno ? errno : -1;
		niortsp_error("failed to calloc");
		goto finally;
	}

	niortsp_debug("constructing");

	RB_INIT(&server->medias);
	RB_INIT(&server->transporters);
	server->evbase = evbase;
	server->mtu = 1500;

	niortsp_debug("creating server");

	if (!(server->server = niortsp_server_new(evbase))) {
		rc = errno ? errno : -1;
		niortsp_error("failed to create server");
		goto finally;
	}

	rc = 0;

finally:
	if (rc != 0) {
		if (server) {
			niortspd_free(server);
			server = NULL;
		}
		errno = rc;
	}
	return server;
}

void niortspd_free(struct niortspd *server)
{
	if (!server) {
		return;
	}

	niortsp_debug("destructing");

	if (server->server) {
		niortsp_server_free(server->server);
	}

	niortp_transport_map_clear(&server->transporters);
	niortsp_media_map_clear(&server->medias);

	free(server);
}

int niortspd_add_media(struct niortspd *server, const char *name,
		niortsp_media_describe_cb describe, void *cbarg)
{
	int rc;
	struct niortsp_media *media;

	if (!server || !name || !describe) {
		rc = EINVAL;
		niortsp_error("null argument(s)");
		goto finally;
	}

	niortsp_debug("adding media: %s", name);

	if ((rc = niortsp_media_map_add(&server->medias, name, &media))) {
		niortsp_error("failed to add: %s", strerror(rc));
		goto finally;
	}

	media->describe = describe;
	media->cbarg = cbarg;
	rc = 0;

finally:
	return rc;
}

int niortspd_remove_media(struct niortspd *server, const char *name)
{
	int rc;
	struct niortsp_media *media;

	if (!server || !name) {
		rc = EINVAL;
		niortsp_error("null argument(s)");
		goto finally;
	}

	if (!(media = niortsp_media_map_find(&server->medias, name))) {
		rc = ENOENT;
		niortsp_error("media not found: %s", name);
		goto finally;
	}

	niortsp_debug("removing media: %s", name);

	niortsp_media_map_remove(&server->medias, media);
	rc = 0;

finally:
	return rc;
}

int niortspd_add_track(struct niortspd *server, const char *media_name,
		const char *name, unsigned char payload_type,
		niortsp_track_describe_cb describe,
		niortsp_track_cb setup, niortsp_track_cb teardown,
		niortsp_track_cb refresh, niortsp_track_finalize_cb finalize,
		void *cbarg)
{
	int rc;
	struct niortsp_media *media;
	struct niortsp_track *track;

	if (!server || !media_name || !name || !describe) {
		rc = EINVAL;
		niortsp_error("null argument(s)");
		goto finally;
	}

	if (!(media = niortsp_media_map_find(&server->medias, media_name))) {
		rc = ENOENT;
		niortsp_error("media not found: %s", media_name);
		goto finally;
	}

	if (niortsp_track_list_find(&media->tracks, name)) {
		rc = EEXIST;
		niortsp_error("track existed: %s/%s", media_name, name);
		goto finally;
	}

	niortsp_debug("adding track: %s/%s", media_name, name);

	if ((rc = niortsp_track_list_add(&media->tracks, name, payload_type,
			&track))) {
		niortsp_error("failed to add track %s/%s: %s", media_name,
				name, strerror(rc));
		goto finally;
	}

	track->media = media;
	track->setup = setup;
	track->teardown = teardown;
	track->describe = describe;
	track->refresh = refresh;
	track->finalize = finalize;
	track->cbarg = cbarg;
	rc = 0;

finally:
	return rc;
}

int niortspd_remove_track(struct niortspd *server, struct niortsp_track *track)
{
	int rc;
	struct niortsp_media *media;

	if (!server || !track) {
		rc = EINVAL;
		niortsp_error("null argument(s)");
		goto finally;
	}

	niortsp_debug("removing track: %p", track);

	niortsp_track_list_del(&media->tracks, track);
	rc = 0;

finally:
	return rc;
}

int niortspd_add_transport(struct niortspd *server,
		enum niortp_transport_type type, unsigned char payload_type,
		struct niortp_transport *transport)
{
	int rc;

	if (!server || !transport) {
		rc = EINVAL;
		niortsp_error("null pointer(s)");
		goto finally;
	}

	if ((rc = niortp_transport_map_add(&server->transporters,
			payload_type, type, transport))) {
		niortsp_error("failed to add: %s", strerror(rc));
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}

int niortspd_bind(struct niortspd *server,
		const char *address, unsigned short port)
{
	int rc, i;

	if (!server || !address) {
		rc = EINVAL;
		goto finally;
	}

	niortsp_debug("binding on %s port %d", address, port);

	if ((rc = niortsp_server_bind(server->server, address, port))) {
		niortsp_error("failed to bind: %s", strerror(rc));
		goto finally;
	}

	niortsp_debug("registering handlers");

	for (i = 0; i < num_handlers; i++) {
		if ((rc = niortsp_server_add_handler(server->server,
				handlers[i].method, niortspd_handler,
				server))) {
			niortsp_error("failed to add handler '%s': %s",
					handlers[i].method, strerror(rc));
			goto finally;
		}
	}

	rc = 0;

finally:
	return rc;
}

int niortspd_match_media_do(struct niortspd *server, const char *media_name,
		const char *track_name, struct niortsp_media **media_ptr,
		struct niortsp_track **track_ptr)
{
	int rc;
	struct niortsp_media *media;
	struct niortsp_track *track;

	niortsp_debug("matching media/track: %s/%s", media_name, track_name);

	if (!(media = niortsp_media_map_find(&server->medias, media_name))) {
		rc = ENOENT;
		goto finally;
	}

	if (!track_name) {
		track = NULL;
	} else if (!(track = niortsp_track_list_find(&media->tracks,
			track_name))) {
		rc = ENOENT;
		niortsp_error("track not found: %s/%s", media_name,
				track_name);
		goto finally;
	}

	*media_ptr = media;
	*track_ptr = track;
	rc = 0;

finally:
	return rc;
}

int niortspd_match_media(struct niortspd *server, const char *path,
		struct niortsp_media **media, struct niortsp_track **track)
{
	int rc, len;
	char *temp, *slash;
	const char *media_name, *track_name;

	temp = NULL;

	niortsp_debug("matching media: %s", path);

	if ((rc = niortspd_match_media_do(server, path, NULL, media, track))) {
		if (rc == ENOENT && (slash = strrchr(path, '/'))) {
			len = slash - path;

			if (!(temp = malloc(len + 1))) {
				rc = errno ? errno : -1;
				niortsp_error("malloc: %s", strerror(rc));
				goto finally;
			}

			memcpy(temp, path, len);
			temp[len] = '\0';
			media_name = temp;
			track_name = slash + 1;

			if ((rc = niortspd_match_media_do(server, media_name,
					track_name, media, track))) {
				goto finally;
			}
		} else {
			goto finally;
		}
	}

	rc = 0;

finally:
	if (temp) {
		free(temp);
	}
	return rc;
}

int niortspd_handler(struct niortsp_session *session,
		struct niortsp_request *req, void *cbarg)
{
	int rc, i;
	struct niortspd *server;
	struct niortsp_url *url;
	struct niortsp_media *media;
	struct niortsp_track *track;
	niortspd_handler_cb cb;

	server = cbarg;
	url = NULL;
	media = NULL;
	track = NULL;

	if (strcmp("*", niortsp_request_get_uri(req))) {
		niortsp_debug("parsing URL: %s", niortsp_request_get_uri(req));

		if (!(url = niortsp_url_new())) {
			rc = errno ? errno : -1;
			niortsp_error("failed to create url parser");
			goto finally;
		}

		if ((rc = niortsp_url_parse(url,
				niortsp_request_get_uri(req)))) {
			niortsp_error("invalid URL: %s", strerror(rc));

			if ((rc = niortsp_session_reply(
					session, req, 400) != 0)) {
				niortsp_error("failed to reply: %s",
						strerror(rc));
				goto finally;
			}

			rc = 0;
			goto finally;
		}

		if (strcmp("rtsp", url->scheme) != 0) {
			niortsp_error("invalid scheme: %s", url->scheme);

			if ((rc = niortsp_session_reply(
					session, req, 400) != 0)) {
				niortsp_error("failed to reply: %s",
						strerror(rc));
				goto finally;
			}

			rc = 0;
			goto finally;
		}

		if (url->path && url->path[0] != '/') {
			niortsp_error("invalid path: %s", url->path);

			if ((rc = niortsp_session_reply(
					session, req, 400) != 0)) {
				niortsp_error("failed to reply: %s",
						strerror(rc));
				goto finally;
			}

			rc = 0;
			goto finally;
		}

		if (url->path && (rc = niortspd_match_media(server,
				url->path + 1, &media, &track))) {
			if (rc == ENOENT) {
				niortsp_error("media not found: %s", url->path);

				if ((rc = niortsp_session_reply(
						session, req, 404) != 0)) {
					niortsp_error("failed to reply: %s",
							strerror(rc));
					goto finally;
				}

				rc = 0;
			} else {
				niortsp_error("failed to match media: %s",
						strerror(rc));
			}
			goto finally;
		}
	}

	cb = NULL;

	for (i = 0; i < num_handlers; i++) {
		if (!strcmp(handlers[i].method,
				niortsp_request_get_method(req))) {
			cb = handlers[i].cb;
			break;
		}
	}

	if (!cb) {
		if ((rc = niortsp_session_reply(session, req, 501) != 0)) {
			niortsp_error("failed to reply: %s", strerror(rc));
			goto finally;
		}

		rc = 0;
		goto finally;
	}

	if ((rc = (*cb)(server, session, req, media, track))) {
		niortsp_error("failed to handle '%s %s': %s",
				niortsp_request_get_method(req),
				niortsp_request_get_uri(req), strerror(rc));
	}

	rc = 0;

finally:
	if (url) {
		niortsp_url_free(url);
	}
	return rc;
}

int niortspd_handle_options(struct niortspd *server,
		struct niortsp_session *session, struct niortsp_request *req,
		struct niortsp_media *media, struct niortsp_track *track)
{
	int rc, i;
	struct evbuffer *buffer = NULL;

	if (!session || !req) {
		niortsp_error("null argument(s)");
		rc = EINVAL;
		goto finally;
	}

	if (!(buffer = evbuffer_new())) {
		rc = errno ? errno : -1;
		niortsp_error("failed to create buffer: %s", strerror(rc));
		goto finally;
	}

	for (i = 0; i < num_handlers; i++) {
		if (i == 0) {
			rc = evbuffer_add_printf(buffer, "%s",
					handlers[i].method);
		} else {
			rc = evbuffer_add_printf(buffer, ", %s",
					handlers[i].method);
		}

		if (rc <= 0) {
			rc = errno ? errno : -1;
			niortsp_error("failed to prepare header: %s",
					strerror(rc));
			goto finally;
		}
	}

	if ((rc = niortsp_request_set_output_header(req, "Public",
			(const char *)EVBUFFER_DATA(buffer)))) {
		niortsp_error("failed to add public header: %s",
				strerror(rc));
		goto finally;
	}

	if ((rc = niortsp_session_reply(session, req, 200) != 0)) {
		niortsp_error("failed to reply: %s", strerror(rc));
		goto finally;
	}

	rc = 0;

finally:
	if (buffer) {
		evbuffer_free(buffer);
	}
	return rc;
}

#define MIME_TYPE_SDP "application/sdp"

int niortspd_handle_describe(struct niortspd *server,
		struct niortsp_session *session, struct niortsp_request *req,
		struct niortsp_media *media, struct niortsp_track *track)
{
	int rc;
	const char *header;
//	struct zcbuffer *buffer = NULL;

	if (!session || !req || !media) {
		niortsp_error("null argument(s)");
		rc = EINVAL;
		goto finally;
	}

	niortsp_debug("describing: %s", media->name);

	if ((header = niortsp_request_get_input_header(req, "Accept")) &&
			!strstr(header, MIME_TYPE_SDP)) {
		niortsp_error("not accepted: "MIME_TYPE_SDP);

		if ((rc = niortsp_session_reply(session, req, 501) != 0)) {
			niortsp_error("failed to reply: %s", strerror(rc));
			goto finally;
		}

		rc = 0;
		goto finally;
	}

	if ((rc = niortsp_media_describe(media, (u_int32_t)session,
			niortsp_request_get_uri(req),
			niortsp_request_get_buffer(req)))) {
		niortsp_error("failed to describe media '%s': %s",
				media->name, strerror(rc));
		goto finally;
	}

	if ((rc = niortsp_request_set_output_header(req,
			"Content-Type", MIME_TYPE_SDP))) {
		niortsp_error("failed to add content-type header: %s",
				strerror(rc));
		goto finally;
	}

	if ((rc = niortsp_session_reply(session, req, 200) != 0)) {
		niortsp_error("failed to reply: %s", strerror(rc));
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}

#define RTP_AVP "RTP/AVP"
#define RTP_AVP_UDP "RTP/AVP/UDP"
#define RTP_AVP_TCP "RTP/AVP/TCP"

#define UNICAST "unicast"
#define MULTICAST "multicast"
#define INTERLEAVED "interleaved="
#define CLIENT_PORT "client_port="

int niortspd_parse_ports(const char *string,
		unsigned short *start, unsigned short *end)
{
	int rc;
	char *ptr;
	long int port_start, port_end;

	errno = 0;
	port_start = strtol(string, &ptr, 10);

	if (errno != 0 || *ptr != '-') {
		niortsp_error("invalid format of start port: %s", string);
		rc = EINVAL;
		goto finally;
	}

	if (port_start < 0 || port_start > 65535) {
		niortsp_error("invalid start port: %s", string);
		rc = EINVAL;
		goto finally;
	}

	errno = 0;
	port_end = strtol(ptr + 1, &ptr, 10);

	if (errno != 0 || (*ptr != ';' && *ptr != '\0')
			|| port_end != port_start + 1) {
		niortsp_error("invalid format of end port: %s", string);
		rc = EINVAL;
		goto finally;
	}

	if (port_end < 0 || port_end > 65535) {
		niortsp_error("invalid end port: %s", string);
		rc = EINVAL;
		goto finally;
	}

	if (port_end - port_start != 1) {
		niortsp_error("invalid range of ports: %ld, %ld",
				port_start, port_end);
		rc = ERANGE;
		goto finally;
	}

	*start = port_start;
	*end = port_end;
	rc = 0;

finally:
	return rc;
}

int niortspd_handle_setup(struct niortspd *server,
		struct niortsp_session *session, struct niortsp_request *req,
		struct niortsp_media *media, struct niortsp_track *track)
{
	int rc;
	unsigned short start, end;
	char *ptr;
	const char *header;
	struct niortp_transport *transport;
	enum niortp_transport_type type;
	char temp[64];

	if (!session || !req || !media) {
		niortsp_error("null argument(s)");
		rc = EINVAL;
		goto finally;
	}

	if (track) {
		niortsp_debug("setting up: %s/%s", media->name, track->name);
	} else {
		niortsp_debug("setting up all tracks: %s", media->name);
	}

	if (!(header = niortsp_request_get_input_header(
			req, "Transport"))) {
		niortsp_error("missing transport header");

		if ((rc = niortsp_session_reply(session, req, 400) != 0)) {
			niortsp_error("failed to reply: %s", strerror(rc));
			goto finally;
		}

		rc = 0;
		goto finally;
	}

	if (!strncmp(header, RTP_AVP_TCP, strlen(RTP_AVP_TCP))) {
		type = NIORTP_TRANSPORT_INTERLEAVED;

		if ((ptr = strstr(header, INTERLEAVED))) {
			if ((rc = niortspd_parse_ports(ptr + strlen(INTERLEAVED),
					&start, &end))) {
				niortsp_error("failed to parse ports from "
						"'%s': %s",
						header, strerror(rc));

				if ((rc = niortsp_session_reply(
						session, req, 400) != 0)) {
					niortsp_error("failed to reply: %s",
							strerror(rc));
					goto finally;
				}

				rc = 0;
				goto finally;
			}
		} else {
			niortsp_warn("interleaving channels not specified; "
					"assigning %d-%d",
					session->num_streams,
					session->num_streams + 1);

			start = session->num_streams;
			end = session->num_streams + 1;
		}
	} else if (!strncmp(header, RTP_AVP, strlen(RTP_AVP)) ||
			!strncmp(header, RTP_AVP_UDP, strlen(RTP_AVP_UDP))) {
		if (strstr(header, MULTICAST)) {
			niortsp_error("unsupported transport: %s", header);

			if ((rc = niortsp_session_reply(session, req, 461) != 0)) {
				niortsp_error("failed to reply: %s",
						strerror(rc));
				goto finally;
			}

			rc = 0;
			goto finally;
		}

		if (!(ptr = strstr(header, CLIENT_PORT))) {
			niortsp_error("unable to find client port in transport "
					"header: %s", header);

			if ((rc = niortsp_session_reply(
					session, req, 400) != 0)) {
				niortsp_error("failed to reply: %s",
						strerror(rc));
				goto finally;
			}

			rc = 0;
			goto finally;
		}

		type = NIORTP_TRANSPORT_UDP_UNICAST;

		if ((rc = niortspd_parse_ports(ptr + strlen(CLIENT_PORT),
				&start, &end))) {
			niortsp_error("failed to parse ports from '%s': %s",
					header, strerror(rc));

			if ((rc = niortsp_session_reply(
					session, req, 400) != 0)) {
				niortsp_error("failed to reply: %s",
						strerror(rc));
				goto finally;
			}

			rc = 0;
			goto finally;
		}
	} else {
		niortsp_error("unsupported transport: %s", header);

		if ((rc = niortsp_session_reply(session, req, 461) != 0)) {
			niortsp_error("failed to reply: %s",
					strerror(rc));
			goto finally;
		}

		rc = 0;
		goto finally;
	}

	niortsp_debug("setting up track: %s/%s", media->name, track->name);

	if (!(transport = niortp_transport_map_find(&server->transporters,
			track->payload_type, type))) {
		rc = errno ? errno : -1;
		niortsp_error("failed to find transport: "
				"payload type %d over %s: %s",
				track->payload_type,
				niortp_transport_type_to_str(type),
				strerror(rc));
		goto finally;
	}

	if ((rc = niortsp_session_setup(session, track, type, transport,
			server->mtu, start, end))) {
		niortsp_error("failed to setup track '%s': %s",
				track->name, strerror(rc));
		goto finally;
	}

	snprintf(temp, sizeof(temp), "%s", header);

	if ((rc = niortsp_request_set_output_header(req,
			"Transport", temp))) {
		niortsp_error("failed to add session header: %s",
				strerror(rc));
		goto finally;
	}

	snprintf(temp, sizeof(temp), "%d", (int)session);

	if ((rc = niortsp_request_set_output_header(req,
			"Session", temp))) {
		niortsp_error("failed to add session header: %s",
				strerror(rc));
		goto finally;
	}

	session->num_streams += 2;

	if ((rc = niortsp_session_reply(session, req, 200) != 0)) {
		niortsp_error("failed to reply: %s", strerror(rc));
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}

int niortspd_handle_play(struct niortspd *server,
		struct niortsp_session *session, struct niortsp_request *req,
		struct niortsp_media *media, struct niortsp_track *track)
{
	int rc;
	long int val;
	char *ptr;
	const char *header;
	struct timeval tv;
	struct evbuffer *buffer = NULL;
	struct niortsp_stream *stream;

	if (!session || !req || !media) {
		niortsp_error("null argument(s)");
		rc = EINVAL;
		goto finally;
	}

	if (track) {
		niortsp_error("only aggregate operation allowed");

		if ((rc = niortsp_session_reply(session, req, 460) != 0)) {
			niortsp_error("failed to reply: %s", strerror(rc));
			goto finally;
		}

		rc = 0;
		goto finally;
	}

	if (TAILQ_EMPTY(&session->streams)) {
		niortsp_error("nothing to play");

		if ((rc = niortsp_session_reply(session, req, 455) != 0)) {
			niortsp_error("failed to reply: %s", strerror(rc));
			goto finally;
		}

		rc = 0;
		goto finally;
	}

	niortsp_debug("playing: %s", media->name);

	if (!(header = niortsp_request_get_input_header(req, "Session"))) {
		niortsp_error("missing session header");

		if ((rc = niortsp_session_reply(session, req, 454) != 0)) {
			niortsp_error("failed to reply: %s", strerror(rc));
			goto finally;
		}

		rc = 0;
		goto finally;
	}

	errno = 0;
	val = strtol(header, &ptr, 10);

	if (errno != 0 || *ptr != '\0' || val != (int)session) {
		niortsp_error("invalid session: %s", header);

		if ((rc = niortsp_session_reply(session, req, 454) != 0)) {
			niortsp_error("failed to reply: %s", strerror(rc));
			goto finally;
		}

		rc = 0;
		goto finally;
	}

	if ((rc = niortsp_session_play(session) != 0)) {
		niortsp_error("failed to play: %s", strerror(rc));
		goto finally;
	}

	if (gettimeofday(&tv, NULL)) {
		rc = errno ? errno : -1;
		niortsp_error("failed to get current time: %s",
				strerror(rc));
		goto finally;
	}

	if (!(buffer = evbuffer_new())) {
		rc = errno ? errno : -1;
		niortsp_error("failed to create buffer: %s", strerror(rc));
		goto finally;
	}

	TAILQ_FOREACH(stream, &session->streams, entry) {
		if (evbuffer_add_printf(buffer, ",url=%s/%s;seq=0;rtptime=%d",
				niortsp_request_get_uri(req),
				stream->track->name,
				niortp_timeval_to_timestamp(&tv)) <= 0) {
			rc = errno ? errno : -1;
			niortsp_error("failed to prepare header: %s",
					strerror(rc));
			goto finally;
		}
	}

	if ((rc = niortsp_request_set_output_header(req,
			"RTP-Info", ((char *)EVBUFFER_DATA(buffer)) + 1))) {
		niortsp_error("failed to add rtp-info header: %s",
				strerror(rc));
		goto finally;
	}

	if ((rc = niortsp_request_set_output_header(req,
			"Range", "npt=0.000-"))) {
		niortsp_error("failed to add range header: %s",
				strerror(rc));
		goto finally;
	}

	if ((rc = niortsp_session_reply(session, req, 200) != 0)) {
		niortsp_error("failed to reply: %s", strerror(rc));
		goto finally;
	}

	rc = 0;

finally:
	if (buffer) {
		evbuffer_free(buffer);
	}
	return rc;
}

int niortspd_handle_teardown(struct niortspd *server,
		struct niortsp_session *session, struct niortsp_request *req,
		struct niortsp_media *media, struct niortsp_track *track)
{
	int rc;
	long int val;
	char *ptr;
	const char *header;

	if (!session || !req || !media) {
		niortsp_error("null argument(s)");
		rc = EINVAL;
		goto finally;
	}

	if (track) {
		niortsp_error("only aggregate operation allowed");

		if ((rc = niortsp_session_reply(session, req, 460) != 0)) {
			niortsp_error("failed to reply: %s", strerror(rc));
			goto finally;
		}

		rc = 0;
		goto finally;
	}

	niortsp_debug("tearing down: %s", media->name);

	if (!(header = niortsp_request_get_input_header(req, "Session"))) {
		niortsp_error("missing session header");

		if ((rc = niortsp_session_reply(session, req, 454) != 0)) {
			niortsp_error("failed to reply: %s", strerror(rc));
			goto finally;
		}

		rc = 0;
		goto finally;
	}

	errno = 0;
	val = strtol(header, &ptr, 10);

	if (errno != 0 || *ptr != '\0' || val != (int)session) {
		niortsp_error("invalid session: %s", header);

		if ((rc = niortsp_session_reply(session, req, 454) != 0)) {
			niortsp_error("failed to reply: %s", strerror(rc));
			goto finally;
		}

		rc = 0;
		goto finally;
	}

	if ((rc = niortsp_session_pause(session) != 0)) {
		niortsp_error("failed to pause: %s", strerror(rc));
		goto finally;
	}

	session->need_close = 1;

	if ((rc = niortsp_session_reply(session, req, 200) != 0)) {
		niortsp_error("failed to reply: %s", strerror(rc));
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}
